#include "snakeio.h"
#include <stdio.h>
#include "cJSON.h"
#include <string.h>
#include "util.h"
#include <winsock2.h>
#include <ws2tcpip.h>

#define MAX_PACKET_SIZE 65536

static int wsa_initialized = 0;


void initPacketRingbuffer(PacketRingBuffer *rb) {
    rb->head = 0;
    rb->tail = 0;
    for (int i = 0; i < 128; i++) {
        rb->buffer[i] = NULL;
    }
}

SOCKET initClientSocket(const char *host, const int port) {
    if (!wsa_initialized) {
        WSADATA wsaData;
        const int wsa_res = WSAStartup(MAKEWORD(2, 2), &wsaData);
        if (wsa_res != 0) {
            fprintf(stderr, "WSAStartup failed: %d\n", wsa_res);
            return INVALID_SOCKET;
        }
        wsa_initialized = 1;
    }

    SOCKET socketFD = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (socketFD == INVALID_SOCKET) {
        fprintf(stderr, "socket failed: %d\n", WSAGetLastError());
        return INVALID_SOCKET;
    }

    struct addrinfo hints = {0}, *res = NULL;
    hints.ai_family = AF_INET; // IPv4
    hints.ai_socktype = SOCK_STREAM;

    const int err = getaddrinfo(host, NULL, &hints, &res);
    if (err != 0 || res == NULL) {
        fprintf(stderr, "getaddr-info failed: %d\n", err);
        closesocket(socketFD);
        return INVALID_SOCKET;
    }
    struct sockaddr_in serv_addr = {0};
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons((u_short) port);
    serv_addr.sin_addr = ((struct sockaddr_in *) res->ai_addr)->sin_addr;

    freeaddrinfo(res);

    if (connect(socketFD, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) == SOCKET_ERROR) {
        fprintf(stderr, "connect failed: %d\n", WSAGetLastError());
        closesocket(socketFD);
        return INVALID_SOCKET;
    }

    return socketFD;
}


bool jsonToPoint(const cJSON *json, Point *point) {
    if (!json || !point) return false;
    if (!cJSON_IsObject(json)) {
        return false;
    }
    const cJSON *x = cJSON_GetObjectItem(json, "x");
    const cJSON *y = cJSON_GetObjectItem(json, "y");
    if (!cJSON_IsNumber(x) || !cJSON_IsNumber(y)) {
        return false;
    }
    point->x = x->valueint;
    point->y = y->valueint;
    return true;
}

bool jsonToSnake(const cJSON *json, Snake *snake) {
    if (!json || !snake) return false;
    // snake 对象
    // 解析 body 数组
    const cJSON *bodyArray = cJSON_GetObjectItem(json, "body");
    if (!cJSON_IsArray(bodyArray)) {
        return false;
    }
    const int len = cJSON_GetArraySize(bodyArray);
    snake->length = len;
    snake->capacity = len;
    snake->body = malloc(sizeof(Point) * len);
    for (int i = 0; i < len; ++i) {
        const cJSON *pos = cJSON_GetArrayItem(bodyArray, i);
        jsonToPoint(pos, &snake->body[i]);
    }
    // direction
    const cJSON *dirItem = cJSON_GetObjectItem(json, "direction");
    if (!dirItem || !cJSON_IsString(dirItem)) {
        return false;
    }
    snake->direction = strToDirection(dirItem->valuestring);
    snake->next_direction = strToDirection(dirItem->valuestring);
    return true;
}

bool jsonToPlayer(const cJSON *json, Player *player) {
    if (!json) return false;
    if (!cJSON_IsObject(json)) {
        return false;
    }
    // playerId
    const cJSON *id = cJSON_GetObjectItem(json, "id");
    if (!cJSON_IsString(id)) {
        return false;
    }
    strCopy(id->valuestring, player->playerId, sizeof(player->playerId));
    //name
    const cJSON *name = cJSON_GetObjectItem(json, "playerName");
    if (!cJSON_IsString(name)) {
        return false;
    }
    strCopy(name->valuestring, player->playerName, sizeof(player->playerName));
    return true;
}

int jsonGetInt(const cJSON *obj, const char *key, int defaultValue) {
    const cJSON *item = cJSON_GetObjectItem(obj, key);
    return (item && cJSON_IsNumber(item)) ? item->valueint : defaultValue;
}

bool jsonToFood(const cJSON *json, Food *food) {
    if (!json || !cJSON_IsObject(json)) return false;
    const cJSON *x = cJSON_GetObjectItem(json, "x");
    const cJSON *y = cJSON_GetObjectItem(json, "y");
    if (!cJSON_IsNumber(x) || !cJSON_IsNumber(y)) return false;
    food->position.x = x->valueint;
    food->position.y = y->valueint;
    return true;
}

uint64_t jsonGetUint64(const cJSON *obj, const char *key, uint64_t defaultVal) {
    const cJSON *item = cJSON_GetObjectItem(obj, key);
    return (item && cJSON_IsNumber(item)) ? (uint64_t) item->valuedouble : defaultVal;
}

bool jsonToPacketFrameItem(const cJSON *json, PacketFrameItem *item) {
    if (!json || !item) return false;

    const cJSON *fType = cJSON_GetObjectItem(json, "type");
    if (!fType || !cJSON_IsString(fType)) return false;

    const PacketType ft = strToPacketType(fType->valuestring);
    item->type = ft;

    if (ft == PACKET_TYPE_MOVE) {
        printf("move packet\n");
    }

    switch (ft) {
        case PACKET_TYPE_DIR: {
            const cJSON *fPlayerId = cJSON_GetObjectItem(json, "playerId");
            const cJSON *fDirection = cJSON_GetObjectItem(json, "direction");
            const cJSON *fIndex = cJSON_GetObjectItem(json, "index");
            const cJSON *fTimestamp = cJSON_GetObjectItem(json, "timestamp");
            if (!fPlayerId || !cJSON_IsString(fPlayerId) ||
                !fDirection || !cJSON_IsString(fDirection) ||
                !fIndex || !cJSON_IsNumber(fIndex) ||
                !fTimestamp || !cJSON_IsNumber(fTimestamp))
                return false;

            item->index = fIndex->valueint;
            item->timestamp = (uint64_t) fTimestamp->valuedouble;
            item->direction = strToDirection(fDirection->valuestring);
            strncpy(item->playerId, fPlayerId->valuestring, sizeof(item->playerId) - 1);
            item->playerId[sizeof(item->playerId) - 1] = '\0';
            break;
        }
        case PACKET_TYPE_MOVE: {
            const cJSON *fIndex = cJSON_GetObjectItem(json, "index");
            const cJSON *fTimestamp = cJSON_GetObjectItem(json, "timestamp");
            if (!fIndex || !cJSON_IsNumber(fIndex) || !fTimestamp || !cJSON_IsNumber(fTimestamp)) return false;
            item->index = fIndex->valueint;
            item->timestamp = (uint64_t) fTimestamp->valuedouble;
            break;
        }
        case PACKET_TYPE_JOIN: {
            const cJSON *fIndex = cJSON_GetObjectItem(json, "index");
            const cJSON *fTimestamp = cJSON_GetObjectItem(json, "timestamp");
            const cJSON *fPlayer = cJSON_GetObjectItem(json, "player");
            if (!fIndex || !cJSON_IsNumber(fIndex) ||
                !fTimestamp || !cJSON_IsNumber(fTimestamp) ||
                !fPlayer || !cJSON_IsObject(fPlayer))
                return false;

            const cJSON *fPlayerId = cJSON_GetObjectItem(fPlayer, "id");
            const cJSON *fPlayerName = cJSON_GetObjectItem(fPlayer, "playerName");
            const cJSON *snakeObj = cJSON_GetObjectItem(fPlayer, "snake");
            if (!cJSON_IsString(fPlayerId) || !cJSON_IsString(fPlayerName) || !cJSON_IsObject(snakeObj)) return false;

            item->index = fIndex->valueint;
            item->timestamp = (uint64_t) fTimestamp->valuedouble;
            strCopy(fPlayerId->valuestring, item->playerId, sizeof(item->playerId));
            strCopy(fPlayerName->valuestring, item->playerName, sizeof(item->playerName));
            if (!jsonToSnake(snakeObj, &item->snake)) {
                return false;
            }
            break;
        }
        case PACKET_TYPE_EXIT: {
            const cJSON *fPlayerId = cJSON_GetObjectItem(json, "playerId");
            const cJSON *fIndex = cJSON_GetObjectItem(json, "index");
            const cJSON *fTimestamp = cJSON_GetObjectItem(json, "timestamp");
            if (!fPlayerId || !cJSON_IsString(fPlayerId) ||
                !fIndex || !cJSON_IsNumber(fIndex) ||
                !fTimestamp || !cJSON_IsNumber(fTimestamp))
                return false;

            item->index = fIndex->valueint;
            item->timestamp = (uint64_t) fTimestamp->valuedouble;
            strCopy(fPlayerId->valuestring,item->playerId, sizeof(item->playerId));
            item->playerId[sizeof(item->playerId) - 1] = '\0';
            break;
        }
        case PACKET_TYPE_FOOD_EATEN: {
            const cJSON *fPlayerId = cJSON_GetObjectItem(json, "playerId");
            const cJSON *fIndex = cJSON_GetObjectItem(json, "index");
            const cJSON *fTimestamp = cJSON_GetObjectItem(json, "timestamp");
            const cJSON *point = cJSON_GetObjectItem(json, "point");
            if (!fPlayerId || !cJSON_IsString(fPlayerId) ||
                !fIndex || !cJSON_IsNumber(fIndex) ||
                !fTimestamp || !cJSON_IsNumber(fTimestamp) ||
                !point || !cJSON_IsObject(point))
                return false;

            if (!jsonToPoint(point, &item->point)) {
                return false;
            }

            item->index = fIndex->valueint;
            item->timestamp = (uint64_t) fTimestamp->valuedouble;
            strncpy(item->playerId, fPlayerId->valuestring, sizeof(item->playerId) - 1);
            item->playerId[sizeof(item->playerId) - 1] = '\0';
            break;
        }
        case PACKET_TYPE_FOOD_CREATE: {
            const cJSON *fIndex = cJSON_GetObjectItem(json, "index");
            const cJSON *fTimestamp = cJSON_GetObjectItem(json, "timestamp");
            const cJSON *point = cJSON_GetObjectItem(json, "point");
            if (!fIndex || !cJSON_IsNumber(fIndex) ||
                !fTimestamp || !cJSON_IsNumber(fTimestamp) ||
                !point || !cJSON_IsObject(point))
                return false;

            if (!jsonToPoint(point, &item->point)) return false;

            item->index = fIndex->valueint;
            item->timestamp = (uint64_t) fTimestamp->valuedouble;
            break;
        }
        default:
            break;
    }
    return true;
}


Packet *stringToPacket(const char *jsonStr) {
    if (!jsonStr) return NULL;
    cJSON *root = cJSON_Parse(jsonStr);
    if (!root) return NULL;
    Packet *pkt = calloc(1, sizeof(Packet));
    if (!pkt) {
        cJSON_Delete(root);
        return NULL;
    }
    // 判断包类型，优先用type字段
    const cJSON *typeItem = cJSON_GetObjectItem(root, "type");
    if (typeItem && cJSON_IsString(typeItem)) {
        pkt->type = strToPacketType(typeItem->valuestring);
        //INIT类型
        if (pkt->type == PACKET_TYPE_INIT) {
            // 解析基础字段
            pkt->init.delay = jsonGetInt(root, "delay", 0);
            pkt->init.gridSize = jsonGetInt(root, "gridSize", 0);
            pkt->init.cellSize = jsonGetInt(root, "cellSize", 0);
            pkt->init.tick = jsonGetInt(root, "tick", 0);
            pkt->init.moveInterval = jsonGetInt(root, "moveInterval", 0);
            pkt->init.index = jsonGetInt(root, "index", 0);
            pkt->init.maxPlayers = jsonGetInt(root, "maxPlayers", 0);

            // 分配并初始化 me 对象
            pkt->init.me = (Player *) malloc(sizeof(Player));
            if (!pkt->init.me) {
                cJSON_Delete(root);
                snake_free_packet(pkt);
                return NULL;
            }
            memset(pkt->init.me, 0, sizeof(Player));

            // 取 me 对象 json
            const cJSON *meObj = cJSON_GetObjectItem(root, "me");
            if (!cJSON_IsObject(meObj)) {
                cJSON_Delete(root);
                snake_free_packet(pkt);
                return NULL;
            }

            // 解析 player 基本信息
            if (!jsonToPlayer(meObj, pkt->init.me)) {
                cJSON_Delete(root);
                snake_free_packet(pkt);
                return NULL;
            }

            // 解析蛇
            const cJSON *snakeJson = cJSON_GetObjectItem(meObj, "snake");
            if (!jsonToSnake(snakeJson, &pkt->init.me->snake)) {
                cJSON_Delete(root);
                snake_free_packet(pkt);
                return NULL;
            }
        }

        //SYNC类型
        if (pkt->type == PACKET_TYPE_SYNC) {
            pkt->sync.playersCount = jsonGetInt(root, "playersCount", 0);
            pkt->sync.tickCounter = jsonGetInt(root, "tickCounter", 0);
            pkt->sync.index = jsonGetInt(root, "index", 0);

            // timestamp 用 double 转 uint64_t
            const cJSON *timestampItem = cJSON_GetObjectItem(root, "timestamp");
            pkt->sync.timestamp = (timestampItem && cJSON_IsNumber(timestampItem))
                                      ? (uint64_t) timestampItem->valuedouble
                                      : 0;

            // 解析 nowFood
            const cJSON *nowFoodItem = cJSON_GetObjectItem(root, "nowFood");
            if (!jsonToFood(nowFoodItem, &pkt->sync.nowFood)) {
                cJSON_Delete(root);
                snake_free_packet(pkt);
                return NULL;
            }
            // 解析 players
            const cJSON *playersArray = cJSON_GetObjectItem(root, "players");
            if (cJSON_IsArray(playersArray)) {
                const int playersCount = cJSON_GetArraySize(playersArray);
                pkt->sync.playersCount = playersCount;
                pkt->sync.players = malloc(playersCount * sizeof(Player *));
                if (!pkt->sync.players) {
                    cJSON_Delete(root);
                    snake_free_packet(pkt);
                    return NULL;
                }

                for (int i = 0; i < playersCount; ++i) {
                    const cJSON *playerJson = cJSON_GetArrayItem(playersArray, i);
                    if (!cJSON_IsObject(playerJson)) {
                        pkt->sync.players[i] = NULL;
                        continue;
                    }
                    Player *p = malloc(sizeof(Player));
                    if (!p) {
                        pkt->sync.players[i] = NULL;
                        continue;
                    }
                    memset(p, 0, sizeof(Player));

                    if (!jsonToPlayer(playerJson, p)) {
                        free(p);
                        pkt->sync.players[i] = NULL;
                        continue;
                    }

                    const cJSON *snakeJson = cJSON_GetObjectItem(playerJson, "snake");
                    if (!jsonToSnake(snakeJson, &p->snake)) {
                        free(p);
                        pkt->sync.players[i] = NULL;
                        continue;
                    }

                    pkt->sync.players[i] = p;
                }
            }

            // 解析 me 玩家
            const cJSON *meObj = cJSON_GetObjectItem(root, "me");
            if (cJSON_IsObject(meObj)) {
                Player *me = malloc(sizeof(Player));
                if (!me) {
                    cJSON_Delete(root);
                    snake_free_packet(pkt);
                    return NULL;
                }
                memset(me, 0, sizeof(Player));

                if (!jsonToPlayer(meObj, me)) {
                    cJSON_Delete(root);
                    snake_free_packet(pkt);
                    free(me);
                    return NULL;
                }

                const cJSON *snakeJson = cJSON_GetObjectItem(meObj, "snake");
                if (!jsonToSnake(snakeJson, &me->snake)) {
                    snake_free_packet(pkt);
                    cJSON_Delete(root);
                    return NULL;
                }
                pkt->sync.me = me;
            }
        }
        //FRAME类型
        if (pkt->type == PACKET_TYPE_FRAME) {
            pkt->frame.index = jsonGetInt(root, "index", 0);
            pkt->frame.timestamp = jsonGetUint64(root, "timestamp", 0);

            const cJSON *framesItem = cJSON_GetObjectItem(root, "frames");
            const int count = (framesItem && cJSON_IsArray(framesItem)) ? cJSON_GetArraySize(framesItem) : 0;
            pkt->frame.frameCount = count;

            if (count > 0) {
                pkt->frame.frames = calloc(count, sizeof(PacketFrameItem));
                if (!pkt->frame.frames) {
                    snake_free_packet(pkt);
                    cJSON_Delete(root);
                    return NULL;
                }
                // 解析每个帧
                for (int i = 0; i < count; i++) {
                    const cJSON *f = cJSON_GetArrayItem(framesItem, i);
                    if (!f) continue;
                    if (!jsonToPacketFrameItem(f, &pkt->frame.frames[i])) {
                        snake_free_packet(pkt);
                        cJSON_Delete(root);
                        return NULL;
                    }
                }
            }
        }
    }
    cJSON_Delete(root);
    return pkt;
}


char *clientPacketToString(const PacketClientSend *pkt) {
    if (!pkt) return NULL;
    if (pkt->type != PACKET_TYPE_DIR &&
        pkt->type != PACKET_TYPE_ESC &&
        pkt->type != PACKET_TYPE_NONE &&
        pkt->type != PACKET_TYPE_INIT &&
        pkt->type != PACKET_TYPE_SYNC) {
        return NULL; // 仅支持客户端发送类型
    }
    cJSON *root = cJSON_CreateObject();
    if (!root) return NULL;

    cJSON_AddStringToObject(root, "type", packetTypeToStr(pkt->type));
    cJSON_AddStringToObject(root, "playerId", pkt->playerId);
    cJSON_AddStringToObject(root, "playerName", pkt->playerName);
    cJSON_AddNumberToObject(root, "index", pkt->index);
    cJSON_AddNumberToObject(root, "timestamp", (double) pkt->timestamp);
    if (pkt->type == PACKET_TYPE_DIR) {
        cJSON_AddStringToObject(root, "direction", directionToStr(pkt->direction));
    }
    char *jsonStr = cJSON_PrintUnformatted(root);
    cJSON_Delete(root);
    return jsonStr;
}

int sendPacket(const SOCKET socketFD, const PacketClientSend *pkt) {
    char *msg = clientPacketToString(pkt);
    const int msg_len = (int) strlen(msg);
    const int packet_len = 4 + msg_len;
    char *packet = malloc(packet_len);
    if (!packet) {
        fprintf(stderr, "malloc failed\n");
        free(msg);
        return -1;
    }
    const uint32_t len_net = htonl(msg_len); // 转成网络字节序
    memcpy(packet, &len_net, 4);
    memcpy(packet + 4, msg, msg_len);
    free(msg);
    int total_sent = 0;
    while (total_sent < packet_len) {
        const int sent = send(socketFD, packet + total_sent, packet_len - total_sent, 0);
        if (sent == SOCKET_ERROR) {
            fprintf(stderr, "send failed: %d\n", WSAGetLastError());
            free(packet);
            return -1;
        }
        total_sent += sent;
    }
    free(packet);
    return 0;
}

void closeClientSocket(const SOCKET socketFD) {
    if (socketFD != INVALID_SOCKET) {
        closesocket(socketFD);
    }
}

void snakeioCleanup() {
    if (wsa_initialized) {
        WSACleanup();
        wsa_initialized = 0;
    }
}

bool ringbuffer_push(PacketRingBuffer *rb, Packet *pkt) {
    if (!rb || !pkt) return false;
    const int next_tail = (rb->tail + 1) % 128;
    if (next_tail == rb->head) {
        return false;
    }
    rb->buffer[rb->tail] = pkt;
    rb->tail = next_tail;
    return true;
}

Packet *ringbuffer_pop(PacketRingBuffer *rb) {
    if (!rb) return NULL;
    if (rb->head == rb->tail)
        return NULL;
    Packet *pkt = rb->buffer[rb->head];
    rb->buffer[rb->head] = NULL;
    rb->head = (rb->head + 1) % 128;
    return pkt;
}

void sendPacketByType(const PacketType type) {
    PacketClientSend pkt;
    pkt.index = cTick;
    pkt.timestamp = getUnixTimestampMillis();
    strncpy(pkt.playerId, localPlayerId, sizeof(pkt.playerId) - 1);
    if (type == PACKET_TYPE_INIT) {
        strncpy(pkt.playerName, playerName, sizeof(pkt.playerName) - 1);
    }
    pkt.type = type;
    sendPacket(sockFD, &pkt);
}

void sendDirPacket(const Direction d) {
    PacketClientSend pkt;
    pkt.index = cTick;
    pkt.timestamp = getUnixTimestampMillis();
    pkt.direction = d;
    pkt.type = PACKET_TYPE_DIR;
    strncpy(pkt.playerId, localPlayerId, sizeof(pkt.playerId) - 1);
    sendPacket(sockFD, &pkt);
}
